# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jaewon

import datetime
import functools

from coin.base.datetime_util import to_datetime

from coin.exchange.binance.kr_rest.product import BinanceProduct
from coin.exchange.bitfinex_v2.kr_rest.product import BitfinexProduct
from coin.exchange.bitflyer_v1.kr_rest.futures_product import BitflyerFuturesProduct
from coin.exchange.bitmex.kr_rest.futures_product import BitmexFuturesProduct
from coin.exchange.gdax.kr_rest.product import GdaxProduct
from coin.exchange.huobi.kr_rest.product import HuobiProduct
from coin.exchange.okex.kr_rest.product import OkexProduct
from coin.exchange.okex_futures.kr_rest.futures_product import OkexFuturesProduct
from coin.exchange.upbit_v1.kr_rest.product import UpbitProduct

from coin.strategy.mm.simple_sim import result_util
# from coin.strategy.mm.simple_sim.strategy.pass_unhedge_3 import PassUnhedgedSimStrategy
from coin.strategy.mm.simple_sim.strategy.pass_unhedge_basis_ratio import PassUnhedgedSimStrategy
from coin.strategy.mm.simple_sim.profile.pass_unhedge_xbtusd_3 import (linear_sell_edge,
                                                                       linear_buy_edge)

ref_product = None
ref_product_true_book_funcs = None
trade_product = None
trade_product_true_book_funcs = None
max_position = None
price_multiplier_1 = None
trade_product_tick = None
trade_qty_func = None


def prepare(args, ref_ts):
  global ref_product, ref_product_true_book_funcs
  global trade_product, trade_product_true_book_funcs
  global max_position, price_multiplier_1, trade_product_tick, trade_qty_func

  try:
    target_currency_str = args[0].upper()
    ref_exchange = args[1]
  except IndexError:
    raise ValueError('Usage: <run command> <currency> <ref_exchange>\n'
                     'Example: <run command> EOS Okex')

  trade_product = OkexFuturesProduct.FromStr('%s-USD.QUARTER' % target_currency_str,
                                             current_datetime=ref_ts)
  trade_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_qty(100.)[1][-1][0]),
                                   (lambda book: book.get_notional_bids_by_qty(100.)[1][-1][0]))
  trade_product_tick = 0.001
  trade_qty_func = (lambda trade: 10 * trade.qty / trade.price)

  if target_currency_str == 'EOS':
    max_position = 1000.
  elif target_currency_str == 'ETH':
    max_position = 20.
  elif target_currency_str == 'BCH':
    max_position = 10.
  else:
    raise ValueError('Unsupported currency: %s' % target_currency_str)

  if ref_exchange == 'Okex':
    ref_product = OkexProduct.FromStr('%s-USDT' % target_currency_str)
    ref_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_amt(10000.)[1][-1][0]),
                                   (lambda book: book.get_notional_bids_by_amt(10000.)[1][-1][0]))

  elif ref_exchange == 'OkexThisWeek':
    ref_product = OkexFuturesProduct.FromStr('%s-USD.THIS_WEEK' % target_currency_str,
                                             current_datetime=ref_ts)
    ref_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_qty(100.)[1][-1][0]),
                                   (lambda book: book.get_notional_bids_by_qty(100.)[1][-1][0]))

  elif ref_exchange == 'Binance':
    ref_product = BinanceProduct.FromStr('%s-USDT' % target_currency_str)
    ref_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_amt(10000.)[1][-1][0]),
                                   (lambda book: book.get_notional_bids_by_amt(10000.)[1][-1][0]))

  elif ref_exchange == 'Bitfinex':
    ref_product = BitfinexProduct.FromStr('%s-USD' % target_currency_str)
    ref_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_amt(10000.)[1][-1][0]),
                                   (lambda book: book.get_notional_bids_by_amt(10000.)[1][-1][0]))

  elif ref_exchange == 'Huobi':
    ref_product = HuobiProduct.FromStr('%s-USDT' % target_currency_str)
    ref_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_amt(10000.)[1][-1][0]),
                                   (lambda book: book.get_notional_bids_by_amt(10000.)[1][-1][0]))

  else:
    raise ValueError('Unknown exchange: %s' % ref_exchange)


def get_products():
  return [ref_product, trade_product]


def get_machines():
  return ['feed-01.cn-hongkong.aliyun']


def get_time_ranges():
  ranges = []
  cur_dt = datetime.datetime(2018, 7, 11, 0, 0, 0)
  end_dt = datetime.datetime(2018, 7, 27, 0, 0, 0)
  while cur_dt < end_dt:
    ranges.append((cur_dt, cur_dt + datetime.timedelta(hours=24)))
    cur_dt += datetime.timedelta(hours=24)
  return ranges


def get_strategy(from_ts, to_ts):
  NS_PER_SECOND = (10**9)
  NS_PER_MINUTE = 60 * NS_PER_SECOND

  strategy_list = []

  for basis_ma_window in [5, 10, 15, 30, 45]:
    for edge_bp in [5, 7, 9, 11, 13]:
      for close_edge_bp in [edge_bp]:
        for agg_edge in [None]:
          for stack in [10, 20, 40]:
            for delay in [1.5, 3., 5.]:
              lot_size = max_position / (stack // 2)

              pricing_param = {
                  'basis_ma_window': basis_ma_window * NS_PER_MINUTE,
                  'tick': trade_product_tick,
                  'book_askt_func_1': ref_product_true_book_funcs[0],
                  'book_bidt_func_1': ref_product_true_book_funcs[1],
                  'book_askt_func_2': trade_product_true_book_funcs[0],
                  'book_bidt_func_2': trade_product_true_book_funcs[1],
                  'price_multiplier_1': price_multiplier_1
              }

              executor_param = {
                  'lot_size': lot_size,
                  'min_pos': -max_position,
                  'max_pos': max_position,
                  'maker_fee': 0. / 10000.,
                  'taker_fee': 2. / 10000.,
                  'execution_delay': delay * NS_PER_SECOND,
                  'post_only': False,
                  'trade_qty_func': trade_qty_func
              }

              name = '%s.%02dm.%02dbp.%02dbp.%02dstack.%.1fdelay.%s' % (
                  str(trade_product.base.symbol),
                  basis_ma_window,
                  edge_bp,
                  close_edge_bp,
                  stack,
                  delay,
                  to_datetime(from_ts).strftime('%Y%m%d'))

              strategy = PassUnhedgedSimStrategy(trade_product,
                                                 ref_product,
                                                 functools.partial(linear_sell_edge,
                                                                   edge_bp / 10000.,
                                                                   close_edge_bp / 10000.,
                                                                   max_position),
                                                 functools.partial(linear_buy_edge,
                                                                   edge_bp / 10000.,
                                                                   close_edge_bp / 10000.,
                                                                   max_position),
                                                 pricing_param,
                                                 executor_param,
                                                 trade_after=basis_ma_window,
                                                 name=name,
                                                 agg_edge=agg_edge,
                                                 feature_filepath=('out/feature.%s.csv' % name),
                                                 fill_filepath=('out/fill.%s.csv' % name))
              strategy_list.append(strategy)

  return strategy_list


def get_strategy_result(strategy):
  return {'name': strategy.name, **strategy.get_summary()}


def aggregate_result(results):
  return result_util.aggregate_sim_result(results)
